
/*
    Software License

    Copyright (C) 2021-05-24  Xoronos

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, version 3.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

/*
    Liabilities

    The software is provided "AS IS" without any warranty of any kind, either expressed,
    implied, or statutory, including, but not limited to, any warranty that the software
    will conform to specifications, any implied warranties of merchantability, fitness
    for a particular purpose, and freedom from infringement, and any warranty that the
    documentation will conform to the software, or any warranty that the software will
    be error free.

    In no event shall Xoronos be liable for any damages, including, but not limited to,
    direct, indirect, special or consequential damages, arising out of, resulting from,
    or in any way connected with this software, whether or not based upon warranty,
    contract, tort, or otherwise, whether or not injury was sustained by persons or
    property or otherwise, and whether or not loss was sustained from, or arose out of
    the results of, or use of, the software or services provided hereunder.

    To request the provided software under a different license you can contact us at
    support@xoronos.com
*/

#include "xrn_miners.h"

#define XRN_RAND_MAX 32767

static unsigned long int xrn_next = 1;

int xrn_rand( void
 )
{
    xrn_next = xrn_next * 1103515245 + 12345;
    return ( unsigned int ) ( xrn_next / 65536 ) % ( XRN_RAND_MAX + 1 );
    // return (((xrn_next = xrn_next * 214013L + 2531011L) >> 16) & 0x7fff);
}

void xrn_srand( unsigned int xrn_seed
 )
{
    xrn_next = xrn_seed;
}

#if ( XNBITS == 256 )

static inline uint64_t xrn_rotl( const uint64_t x, int k
 )
{
    return ( x << k ) | ( x >> ( 64 - k ) );
}

static uint64_t s[4];
uint64_t xrn_xorshiro( void
 )
{
    const uint64_t result = xrn_rotl( s[0] + s[3], 23 ) + s[0];

    const uint64_t t = s[1] << 17;

    s[2] ^= s[0];
    s[3] ^= s[1];
    s[1] ^= s[2];
    s[0] ^= s[3];

    s[2] ^= t;

    s[3] = xrn_rotl( s[3], 45 );

    return result;
}

#elif ( XNBITS == 128 )

static inline uint64_t xrn_rotl( const uint64_t x, int k
 )
{
    return ( x << k ) | ( x >> ( 64 - k ) );
}

static uint64_t s[2];
uint64_t xrn_xorshiro( void
 )
{
    const uint64_t s0 = s[0];
    uint64_t s1 = s[1];
    const uint64_t result = xrn_rotl( s0 + s1, 17 ) + s0;

    s1 ^= s0;
    s[0] = xrn_rotl( s0, 49 ) ^ s1 ^ ( s1 << 21 );      // a, b
    s[1] = xrn_rotl( s1, 28 );  // c

    return result;
}

#endif

int xrn_mine_stocastic( unsigned seed, xgenesis_base_t * ob, int maxn, int minn
 )
{
    XPRINT_DEB(  );

    xmat_t m;

    int tap, i, j, k;
    int lower, upper, error;

    int rndn[XNBITS], nnum;
    XBIT_TYPE done;

    // initialize randomness
    xrn_srand( seed );

    // initialize xgenesis base
    xrn_initialize_genesis_base( ob );
    xgenesis_base_v2m( ob, &m );

    // calculate tap
    tap = ( ( ( sizeof( xnum_t ) * 8 ) - ( ( sizeof( xnum_t ) * 8 ) % 3 ) ) / 3 );

    lower = tap + 1;
    upper = ( sizeof( xnum_t ) * 8 ) - 1;

    // for the first row get only one 1
    if ( maxn == minn ) {
        nnum = maxn;
    } else {
        nnum = ( xrn_rand(  ) % ( maxn - minn + 1 ) ) + minn;
    }

    // initialize to zero the random numbers
    for ( j = 0; j < nnum - 1; j++ ) {
        rndn[j] = 0;
    }

    // get nnum - 1 random numbers
    for ( j = 0; j < nnum - 1; j++ ) {

        do {
            // assign a random number
            rndn[j] = ( xrn_rand(  ) % ( upper - lower + 1 ) ) + lower;

            // check that the number is unique
            done = 0;
            for ( k = 0; ( k < j ) && ( !done ); k++ ) {
                if ( rndn[j] == rndn[k] ) {
                    done = 1;
                }

            }

        } while ( done );

    }

    // assign the random bits
    for ( j = 0; j < nnum - 1; j++ ) {
        BIT_SETVAL( m.xmat[0], rndn[j], 1 );
    }

    // for all the other rows add 2 ones
    for ( i = 1; i < tap; i++ ) {

        // for the first row get only one 1
        if ( maxn == minn ) {
            nnum = maxn;
        } else {
            nnum = ( xrn_rand(  ) % ( maxn - minn + 1 ) ) + minn;
        }

        // initialize to zero the random numbers
        for ( j = 0; j < nnum; j++ ) {
            rndn[j] = 0;
        }

        // get nnum - 1 random numbers
        for ( j = 0; j < nnum; j++ ) {

            do {
                // assign a random number
                rndn[j] = ( xrn_rand(  ) % ( upper - lower + 1 ) ) + lower;

                // check that the number is unique
                done = 0;
                for ( k = 0; ( k < j ) && ( !done ); k++ ) {
                    if ( rndn[j] == rndn[k] ) {
                        done = 1;
                    }

                }

            } while ( done );

        }

        // assign the random bits
        for ( j = 0; j < nnum; j++ ) {
            BIT_SETVAL( m.xmat[i], rndn[j], 1 );
        }

    }

    xgenesis_base_m2v( &m, ob );
    error = xrn_check_genesis_base( ob );

    return error;
}

int xrn_mine_random( unsigned seed, xgenesis_base_t * ob, float prob
 )
{
    XPRINT_DEB(  );

    int i, j, error, randn;
    xgenesis_base_t ob_tmp;
    XBIT_TYPE bit;

    // initialize randomness
    xrn_srand( seed );

    // initialize xgenesis base
    xrn_initialize_genesis_base( &ob_tmp );

    for ( i = 0; i < XNBYTES_ORIGIN; i++ ) {

        for ( j = 0; j < 8; j++ ) {
            randn = xrn_rand(  );
            bit = randn / ( double ) XRN_RAND_MAX < prob;
            ob_tmp.base_vector[i] = ( uint8_t ) ( ob_tmp.base_vector[i] | ( bit << j ) );
        }

    }

    *ob = ob_tmp;
    error = xrn_check_genesis_base( ob );

    return error;

}

int xrn_mine_random_wrapper( FILE * fp, float prob, unsigned max_try, uint8_t pseudo_rnd_mining_en,
                             FILE * seed_fp, xrn_crypto_extra_settings_t settings
 )
{
    XPRINT_DEB(  );
    int miner_try, i;
    uint8_t err_settings, war_settings;
    time_t t, rnd;
    xrn_matrix_compressed_t xmc;
    xrn_matrix_t xm;
    int ret, final_ret, valid;
    xrn_point_t point_seed;

    if ( fp == NULL ) {
        XPRINT_ERR( 2210, XERR_NULL_MSG );
        return XERR_NULL;
    }

    if ( ( pseudo_rnd_mining_en == 1 ) && ( seed_fp == NULL ) ) {
        XPRINT_ERR( 2211, XERR_NULL_MSG );
        return XERR_NULL;
    }

    for ( i = 0; i < sizeof( xm ); i++ ) {
        ( ( uint8_t * ) & xm )[i] = 0;
    }

    for ( i = 0; i < sizeof( xmc ); i++ ) {
        ( ( uint8_t * ) & xmc )[i] = 0;
    }

    for ( i = 0; i < sizeof( point_seed ); i++ ) {
        ( ( uint8_t * ) & point_seed )[i] = 0;
    }

    final_ret = XSUCCESS;
    valid = 0;

    if ( pseudo_rnd_mining_en == 0 ) {

        if ( get_random_bytes( ( uint8_t * ) & rnd, sizeof( rnd ) ) != XSUCCESS ) {
            XPRINT_ERR( 2212, XERR_IO_MSG );
            return XERR_IO;
        }

        miner_try = 0;
        while ( ( miner_try <= max_try )
                && xrn_mine_random( ( unsigned ) ( time( &t ) ^ rnd ), &xmc.xgenesis_base, prob ) ) {
            miner_try++;
        }

    } else if ( pseudo_rnd_mining_en == 1 ) {

        ret = xrn_load_point( &point_seed, seed_fp, settings );

        final_ret = xrn_store_warnings_in_return_value( ret, final_ret );
        if ( final_ret == XERR_GENERAL ) {
            XPRINT_ERR( 2213, XERR_GENERAL_MSG );
            return ret;
        }

#if ( XNBITS == 256 )

        for ( i = 0; i < 4; i++ ) {
            s[i] = ( ( uint64_t * ) & point_seed.point.xnum[0] )[i];
        }

#elif ( XNBITS == 128 )

        for ( i = 0; i < 2; i++ ) {
            s[i] = ( ( uint64_t * ) & point_seed.point.xnum[0] )[i];
        }

#endif

        miner_try = 0;
        while ( ( miner_try <= max_try )
                && xrn_mine_random( ( unsigned ) s[0], &xmc.xgenesis_base, prob ) ) {
            miner_try++;
            xrn_xorshiro(  );
        }

    } else {

        XPRINT_ERR( 2214, XERR_TYPE_MSG );
        return XERR_TYPE;

    }

    miner_try = 0;
    while ( ( miner_try <= max_try ) && ( valid == 0 ) ) {
        if ( pseudo_rnd_mining_en == 0 ) {

            do {

                if ( get_random_bytes( ( uint8_t * ) & xmc.change_of_sign, sizeof( xmc.change_of_sign ) ) !=
                     XSUCCESS ) {
                    XPRINT_ERR( 710, XERR_IO_MSG );
                    return XERR_IO;
                }

            } while ( xrn_check_sparsity( xmc.change_of_sign ) != XSUCCESS );

            do {

                if ( get_random_bytes( ( uint8_t * ) & xmc.change_of_base, sizeof( xmc.change_of_base ) ) !=
                     XSUCCESS ) {
                    XPRINT_ERR( 711, XERR_IO_MSG );
                    return XERR_IO;
                }

            } while ( xrn_check_sparsity( xmc.change_of_base ) != XSUCCESS );

        } else if ( pseudo_rnd_mining_en == 1 ) {

#if ( XNBITS == 256 )

            for ( i = 0; i < XNBITS * 2; i++ ) {
                xrn_xorshiro(  );
            }

            for ( i = 0; i < 4; i++ ) {
                ( ( uint64_t * ) & xmc.change_of_sign.xnum[0] )[i] = s[i];
            }

            for ( i = 0; i < XNBITS * 2; i++ ) {
                xrn_xorshiro(  );
            }

            for ( i = 0; i < 4; i++ ) {
                ( ( uint64_t * ) & xmc.change_of_base.xnum[0] )[i] = s[i];
            }

#elif ( XNBITS == 128 )

            for ( i = 0; i < XNBITS * 2; i++ ) {
                xrn_xorshiro(  );
            }

            for ( i = 0; i < 2; i++ ) {
                ( ( uint64_t * ) & xmc.change_of_sign.xnum[0] )[i] = s[i];
            }

            for ( i = 0; i < XNBITS * 2; i++ ) {
                xrn_xorshiro(  );
            }

            for ( i = 0; i < 2; i++ ) {
                ( ( uint64_t * ) & xmc.change_of_base.xnum[0] )[i] = s[i];
            }

#endif

        }

        err_settings = xlog.err_en;
        war_settings = xlog.war_en;
        xlog.war_en = 0;
        xlog.err_en = 0;

        xrn_matrix_decompress( &xmc, &xm );
        if ( xrn_matrix_check( &xm ) == XSUCCESS ) {
            valid = 1;
        }

        xlog.err_en = err_settings;
        xlog.war_en = war_settings;

        miner_try++;

    }

    if ( miner_try == max_try ) {
        final_ret = xrn_store_warnings_in_return_value( ( int ) XWAR_TRY, final_ret );
    }

    ret = xrn_dump_matrix_compressed( &xmc, fp, settings );
    final_ret = xrn_store_warnings_in_return_value( ret, final_ret );
    if ( final_ret == XERR_GENERAL ) {
        XPRINT_ERR( 2215, XERR_GENERAL_MSG );
        return ret;
    }

    return final_ret;

}

int xrn_mine_random_settings( float *prob, XBIT_TYPE check
 )
{
    XPRINT_DEB(  );
    if ( prob == NULL ) {
        XPRINT_ERR( 2216, XERR_NULL_MSG );
        return XERR_NULL;
    } else if ( check == 0 ) {
        *prob = ( float ) 0.005;
    } else {
        if ( ( *prob > 0.5 ) || ( *prob < 0.001 ) ) {
            XPRINT_ERR( 2217, XERR_SETTINGS_MSG );
            return XERR_SETTINGS;
        }

    }

    return XSUCCESS;
}

int xrn_check_set_stocastic_settings( int *maxn, int *minn, XBIT_TYPE check
 )
{
    XPRINT_DEB(  );
    if ( ( maxn == NULL ) || ( maxn == NULL ) ) {
        XPRINT_ERR( 2218, XERR_NULL_MSG );
        return XERR_NULL;
    } else if ( check == 0 ) {
        *maxn = 5;
        *minn = 2;
    } else {
        if ( ( *maxn > 15 ) || ( *minn > *maxn ) || ( *minn < 0 )
             || ( *maxn < 0 ) ) {
            XPRINT_ERR( 2219, XERR_SETTINGS_MSG );
            return XERR_SETTINGS;
        }

    }

    return XSUCCESS;
}

int xrn_mine_stocastic_wrapper( FILE * fp, int maxn, int minn, unsigned max_try,
                                uint8_t pseudo_rnd_mining_en, FILE * seed_fp,
                                xrn_crypto_extra_settings_t settings
 )
{
    XPRINT_DEB(  );

    int i, miner_try, ret, final_ret, valid;
    time_t t, rnd;
    uint8_t err_settings, war_settings;
    xrn_matrix_compressed_t xmc;
    xrn_matrix_t xm;
    xrn_point_t point_seed;

    if ( fp == NULL ) {
        XPRINT_ERR( 2220, XERR_NULL_MSG );
        return XERR_NULL;
    }

    if ( ( pseudo_rnd_mining_en == 1 ) && ( seed_fp == NULL ) ) {
        XPRINT_ERR( 2221, XERR_NULL_MSG );
        return XERR_NULL;
    }

    final_ret = XSUCCESS;
    valid = 0;

    for ( i = 0; i < sizeof( xm ); i++ ) {
        ( ( uint8_t * ) & xm )[i] = 0;
    }

    for ( i = 0; i < sizeof( xmc ); i++ ) {
        ( ( uint8_t * ) & xmc )[i] = 0;
    }

    for ( i = 0; i < sizeof( point_seed ); i++ ) {
        ( ( uint8_t * ) & point_seed )[i] = 0;
    }

    if ( pseudo_rnd_mining_en == 0 ) {

        if ( get_random_bytes( ( uint8_t * ) & rnd, sizeof( rnd ) ) != XSUCCESS ) {
            XPRINT_ERR( 2222, XERR_IO_MSG );
            return XERR_IO;
        }

        miner_try = 0;
        while ( ( miner_try <= max_try )
                && xrn_mine_stocastic( ( unsigned ) ( time( &t ) ^ rnd ), &xmc.xgenesis_base, maxn, minn ) ) {
            miner_try++;
        }

    } else if ( pseudo_rnd_mining_en == 1 ) {

        ret = xrn_load_point( &point_seed, seed_fp, settings );

        final_ret = xrn_store_warnings_in_return_value( ret, final_ret );
        if ( final_ret == XERR_GENERAL ) {
            XPRINT_ERR( 2223, XERR_GENERAL_MSG );
            return ret;
        }

#if ( XNBITS == 256 )

        for ( i = 0; i < 4; i++ ) {
            s[i] = ( ( uint64_t * ) & point_seed.point.xnum[0] )[i];
        }

#elif ( XNBITS == 128 )

        for ( i = 0; i < 2; i++ ) {
            s[i] = ( ( uint64_t * ) & point_seed.point.xnum[0] )[i];
        }

#endif

        miner_try = 0;
        while ( ( miner_try <= max_try )
                && xrn_mine_stocastic( ( unsigned ) s[0], &xmc.xgenesis_base, maxn, minn ) ) {
            miner_try++;
            xrn_xorshiro(  );
        }

    } else {

        XPRINT_ERR( 2224, XERR_TYPE_MSG );
        return XERR_TYPE;

    }

    miner_try = 0;
    while ( ( miner_try <= max_try ) && ( valid == 0 ) ) {

        if ( pseudo_rnd_mining_en == 0 ) {

            do {

                if ( get_random_bytes( ( uint8_t * ) & xmc.change_of_sign, sizeof( xmc.change_of_sign ) ) !=
                     XSUCCESS ) {
                    XPRINT_ERR( 704, XERR_IO_MSG );
                    return XERR_IO;
                }

            } while ( xrn_check_sparsity( xmc.change_of_sign ) != XSUCCESS );

            do {

                if ( get_random_bytes( ( uint8_t * ) & xmc.change_of_base, sizeof( xmc.change_of_base ) ) !=
                     XSUCCESS ) {
                    XPRINT_ERR( 708, XERR_IO_MSG );
                    return XERR_IO;
                }

            } while ( xrn_check_sparsity( xmc.change_of_base ) != XSUCCESS );

        } else if ( pseudo_rnd_mining_en == 1 ) {

#if ( XNBITS == 256 )

            for ( i = 0; i < XNBITS * 2; i++ ) {
                xrn_xorshiro(  );
            }

            for ( i = 0; i < 4; i++ ) {
                ( ( uint64_t * ) & xmc.change_of_sign.xnum[0] )[i] = s[i];
            }

            for ( i = 0; i < XNBITS * 2; i++ ) {
                xrn_xorshiro(  );
            }

            for ( i = 0; i < 4; i++ ) {
                ( ( uint64_t * ) & xmc.change_of_base.xnum[0] )[i] = s[i];
            }

#elif ( XNBITS == 128 )

            for ( i = 0; i < XNBITS * 2; i++ ) {
                xrn_xorshiro(  );
            }

            for ( i = 0; i < 2; i++ ) {
                ( ( uint64_t * ) & xmc.change_of_sign.xnum[0] )[i] = s[i];
            }

            for ( i = 0; i < XNBITS * 2; i++ ) {
                xrn_xorshiro(  );
            }

            for ( i = 0; i < 2; i++ ) {
                ( ( uint64_t * ) & xmc.change_of_base.xnum[0] )[i] = s[i];
            }

#endif

        }

        err_settings = xlog.err_en;
        war_settings = xlog.war_en;

        xlog.war_en = 0;
        xlog.err_en = 0;

        xrn_matrix_decompress( &xmc, &xm );
        if ( XSUCCESS == xrn_matrix_check( &xm ) ) {
            valid = 1;
        }

        xlog.err_en = err_settings;
        xlog.war_en = war_settings;

        miner_try++;

    }

    if ( miner_try == max_try ) {
        final_ret = xrn_store_warnings_in_return_value( ( int ) XWAR_TRY, final_ret );
    }

    ret = xrn_dump_matrix_compressed( &xmc, fp, settings );
    final_ret = xrn_store_warnings_in_return_value( ret, final_ret );
    if ( final_ret == XERR_GENERAL ) {
        XPRINT_ERR( 2225, XERR_GENERAL_MSG );
        return ret;
    }

    return final_ret;

}

